/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is ThumbsDown.
 *
 * The Initial Developer of the Original Code is Atte Kemppilä.
 * Portions created by the Initial Developer are Copyright (C) 2011
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */


const EXPORTED_SYMBOLS = ["ScriptStore"];

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/Services.jsm");

Cu.import("resource://thumbsdown/Scripts.jsm");
Cu.import("resource://thumbsdown/Sqlite.jsm");


const ScriptStore =
{
    _options:
    {
        filename: "thumbsdown.sqlite",
        schemaVersion: 7,
        schemaScript: "resource://thumbsdown/db/schema.sql",
        migrationScript: "resource://thumbsdown/db/migrate-$.sql",
        stmt:
        {
            getScripts: "SELECT id, name, disabled, type, subType, thumbnailType, thumbnailPattern, linkType, linkExpr, linkPattern, action1, action2, created, modified, lastUsed, counter FROM scripts ORDER BY name, id",
            getScript: "SELECT id, name, disabled, type, subType, thumbnailType, thumbnailPattern, linkType, linkExpr, linkPattern, action1, action2, created, modified, lastUsed, counter FROM scripts WHERE id = :value",
            addScript: "INSERT INTO scripts (name, disabled, type, subType, thumbnailType, thumbnailPattern, linkType, linkExpr, linkPattern, action1, action2, created, modified) VALUES (:name, :disabled, :type, :subType, :thumbnailType, :thumbnailPattern, :linkType, :linkExpr, :linkPattern, :action1, :action2, strftime('%s','now') * 1000, strftime('%s','now') * 1000)",
            setScript: "UPDATE scripts SET name = :name, disabled = :disabled, type = :type, subType = :subType, thumbnailType = :thumbnailType, thumbnailPattern = :thumbnailPattern, linkType = :linkType, linkExpr = :linkExpr, linkPattern = :linkPattern, action1 = :action1, action2 = :action2, modified = strftime('%s','now') * 1000 WHERE id = :id",
            setScriptDisabled: "UPDATE scripts SET disabled = :disabled WHERE id = :id",
            setScriptStats: "UPDATE scripts SET lastUsed = :lastUsed, counter = :counter WHERE id = :id",
            removeScript: "DELETE FROM scripts WHERE id = :value",
        },
    },
    
    TOPIC: "ThumbsDown.ScriptStore",  // data: {type: <TOPIC_TYPE_???>, ...}
    TOPIC_TYPE_ADDED:    "added",     // data: {ids: <list of add script ids>}
    TOPIC_TYPE_MODIFIED: "modified",  // data: {ids: <list of modified script ids>}
    TOPIC_TYPE_REMOVED:  "removed",   // data: {ids: <list of removed script ids>}
    
    open: function()
    {
        if (this._sqlite)
            return;
        
        this._sqlite = new Sqlite(this._options);
        this._sqlite.open();
        
        Services.obs.addObserver(this, "quit-application-granted", false);
    },
    
    close: function()
    {
        if (! this._sqlite)
            return;
        
        Services.obs.removeObserver(this, "quit-application-granted");
        
        this.flush();
        this._sqlite.close();
        delete this._sqlite;
        delete this._scripts;
        delete this._scriptIndex;
        delete this._enabled;
    },
    
    // nsIObserver
    observe: function()
    {
        // quit-application-granted
        this.flush();
    },
    
    flush: function()
    {
        if (! this._scripts)
            return;
        
        this._sqlite.tx(this, function()
        {
            for each (let s in this._scripts)
                if (s._dirty)
                {
                    let params = {id: s.id, lastUsed: s.lastUsed.getTime(), counter: s.counter};
                    this._sqlite.setScriptStats(params).execute()
                }
        });
    },
    
    getScripts: function(all)
    {
        if (! this._scripts)
        {
            let records = this._sqlite.getScripts().getAll();
            this._scripts = [this._createScript(record) for each (record in records)];
        }
        
        if (all)
            return this._scripts;
        
        if (! this._enabled)
            this._enabled = [script for each (script in this._scripts) if (! script.disabled)];
        
        return this._enabled;
    },
    
    getScript: function(id)
    {
        let index = this._getScriptIndex(id);
        return ((index != -1) ? this.getScripts(true)[index] : null);
    },
    
    _getScript: function(id)
    {
        let record = this._sqlite.getScript(id).getOne();
        let script = this._createScript(record);
        return script;
    },
    
    _getScriptIndex: function(id)
    {
        if (! this._scriptIndex)
        {
            let scripts = this.getScripts(true);
            let scriptIndex = {};
            
            for (let index = 0; index < scripts.length; index++)
                scriptIndex[scripts[index].id] = index;
            
            this._scriptIndex = scriptIndex;
        }
        
        return ((id in this._scriptIndex) ? this._scriptIndex[id] : -1);
    },
    
    addScript: function({name, disabled, type, subType, thumbnailType, thumbnailPattern, linkType, linkExpr, linkPattern, action1, action2})
    {
        let record = {name: name, disabled: disabled, type: type, subType: subType, thumbnailType: thumbnailType, thumbnailPattern: thumbnailPattern, linkType: linkType, linkExpr: linkExpr, linkPattern: linkPattern, action1: action1, action2: action2};
        let id = this._sqlite.addScript(record).execute();
        let script = this._getScript(id);
        
        if (this._scripts)
        {
            this._scripts.push(script);
            
            if (this._scriptIndex)
                this._scriptIndex[id] = this._scripts.length - 1;
            
            if (this._enabled && (! script.disabled))
                this._enabled.push(script);
        }
        
        this._notifyObservers({type: this.TOPIC_TYPE_ADDED, ids: [id]});
        
        return script;
    },
    
    setScript: function({id, name, disabled, type, subType, thumbnailType, thumbnailPattern, linkType, linkExpr, linkPattern, action1, action2})
    {
        let record = {id: id, name: name, disabled: disabled, type: type, subType: subType, thumbnailType: thumbnailType, thumbnailPattern: thumbnailPattern, linkType: linkType, linkExpr: linkExpr, linkPattern: linkPattern, action1: action1, action2: action2};
        this._sqlite.setScript(record).execute();
        let script = this._updateScript(id);
        return script;
    },
    
    setScriptDisabled: function(id, disabled)
    {
        let record = {id: id, disabled: disabled};
        this._sqlite.setScriptDisabled(record).execute();
        let script = this._updateScript(id);
        return script;
    },
    
    _updateScript: function(id)
    {
        this.flush();
        let script = this._getScript(id);
        
        if (this._scripts)
        {
            let index = this._getScriptIndex(id);
            this._scripts[index] = script;
            delete this._enabled;
        }
        
        this._notifyObservers({type: this.TOPIC_TYPE_MODIFIED, ids: [id]});
        
        return script;
    },
    
    removeScripts: function(ids)
    {
        if (ids.length == 0)
            return;
        
        this._sqlite.tx(this, function()
        {
            for each (let id in ids)
                this._sqlite.removeScript(id).execute();
        });
        
        if (this._scripts)
        {
            let scripts = this._scripts;
            for each (let id in ids)
                for (let index = scripts.length - 1; index >= 0; index--)
                    if (scripts[index].id == id)
                    {
                        scripts.splice(index, 1);
                        break;
                    }
            
            delete this._scriptIndex;
            delete this._enabled;
        }
        
        this._notifyObservers({type: this.TOPIC_TYPE_REMOVED, ids: ids});
    },
    
    _notifyObservers: function(data)
    {
        Services.obs.notifyObservers(this, this.TOPIC, JSON.stringify(data));
    },
    
    _createScript: function(record)
    {
        switch (record.type)
        {
            case "direct":  return new DirectScript(record);
            case "regexp":  return new RegexpScript(record);
            case "custom":  return new CustomScript(record);
            case "wrapped": return new WrappedScript(record);
            default: throw new Error(record.type);
        }
    },
}
